﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Svg2Xaml;

namespace AIStudio.Wpf.Svg2XamlBrowser
{
    //****************************************************************************
    public partial class MainWindow : Window
    {
        //==========================================================================
        private volatile Thread m_LoadThread = null;
        private readonly Queue<string> m_LoadFiles = new Queue<string>();

        //==========================================================================
        public MainWindow()
        {
            InitializeComponent();
        }

        //==========================================================================
        private void SetProgress(double progressValue, string text, params object[] args)
        {
            Dispatcher.Invoke((Action)delegate {
                StatusProgressBar.Visibility = Visibility.Visible;
                StatusProgressBar.Value = progressValue;
                StatusTextBlock.Text = String.Format(text, args);
            },
            DispatcherPriority.Background);
        }

        //==========================================================================
        [STAThread]
        private void LoadFiles(object arg)
        {
            int thumbnail_size = default(int);
            Dispatcher.Invoke((Action)delegate {
                thumbnail_size = ThumbnailSize;
            });

            m_LoadThread = Thread.CurrentThread;

            int successful = 0;
            int errorneous = 0;

            while (true)
            {
                string file_name = null;
                int count;
                lock (m_LoadFiles)
                {
                    count = m_LoadFiles.Count;
                    if (count > 0)
                        file_name = m_LoadFiles.Dequeue();
                }
                if (file_name == null)
                    break;

                try
                {
                    SetProgress(100.0 * DirectoryEntriesListBox.Items.Count / (DirectoryEntriesListBox.Items.Count + count),
                                "Loading {0}...", System.IO.Path.GetFileName(file_name));

                    DrawingImage svg_image = null;

                    if (file_name.EndsWith("svgz"))
                        using (FileStream file_stream = new FileStream(file_name, FileMode.Open, FileAccess.Read))
                        using (GZipStream gzip_stream = new GZipStream(file_stream, CompressionMode.Decompress))
                            svg_image = SvgReader.Load(gzip_stream, new SvgReaderOptions(true));
                    else
                        using (FileStream file_stream = new FileStream(file_name, FileMode.Open, FileAccess.Read))
                            svg_image = SvgReader.Load(file_stream, new SvgReaderOptions(true));


                    DrawingVisual visual = new DrawingVisual();
                    using (DrawingContext drawing_context = visual.RenderOpen())
                        drawing_context.DrawRectangle(
                          new ImageBrush(svg_image) { Stretch = Stretch.Uniform }, null, new Rect(0, 0, thumbnail_size, thumbnail_size));
                    RenderTargetBitmap render_bitmap = new RenderTargetBitmap(thumbnail_size, thumbnail_size, 96, 96, PixelFormats.Pbgra32);
                    render_bitmap.Render(visual);
                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(render_bitmap));

                    using (MemoryStream stream = new MemoryStream())
                    {
                        encoder.Save(stream);

                        BitmapImage bmp_image = new BitmapImage();
                        bmp_image.BeginInit();
                        bmp_image.StreamSource = stream;
                        bmp_image.CacheOption = BitmapCacheOption.OnLoad;
                        bmp_image.EndInit();
                        bmp_image.Freeze();

                        Dispatcher.Invoke((Action)delegate {
                            Image image = new Image();
                            image.Width = thumbnail_size;
                            image.Height = thumbnail_size;
                            image.Source = bmp_image;

                            ListBoxItem item = new ListBoxItem();
                            item.Content = image;
                            item.ToolTip = System.IO.Path.GetFileName(file_name);
                            item.Tag = file_name;
                            item.PreviewMouseDoubleClick += DirectoryEntriesListBox_Item_PreviewMouseDoubleClick;
                            DirectoryEntriesListBox.Items.Add(item);
                        }, DispatcherPriority.Background);

                        ++successful;
                    }

                }
                catch (Exception exception)
                {
                    Dispatcher.Invoke((Action)delegate {
                        Border border = new Border();
                        border.Width = thumbnail_size;
                        border.Height = thumbnail_size;
                        border.Background = Brushes.Red;
                        border.IsHitTestVisible = false;

                        ListBoxItem item = new ListBoxItem();
                        item.Content = border;
                        item.ToolTip = System.IO.Path.GetFileName(file_name);
                        item.Tag = file_name;
                        DirectoryEntriesListBox.Items.Add(item);

                        JournalListBox.Items.Add(String.Format("{0}: {1}", file_name, exception.Message));

                    }, DispatcherPriority.Background);

                    ++errorneous;
                }
            }

            Dispatcher.Invoke((Action)delegate {

                StatusProgressBar.Visibility = Visibility.Collapsed;
                StatusProgressBar.Value = 0;
                StatusTextBlock.Text = String.Format("Successfully loaded {0} files, {1} files could not be loaded properly", successful, errorneous);

            }, DispatcherPriority.Background);

            m_LoadThread = null;
        }

        //==========================================================================
        void DirectoryEntriesListBox_Item_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            ListBoxItem item = sender as ListBoxItem;
            if (item != null)
                new PreviewWindow(item.Tag as string).Show();
        }

        //==========================================================================
        private void UpdateDirectoryTreeView()
        {
            DirectoryTreeView.Items.Clear();

            foreach (string drive in Directory.GetLogicalDrives())
            {
                TreeViewItem drive_item = new TreeViewItem();
                drive_item.Header = drive;
                drive_item.Expanded += DirectoryTreeViewItemExpanded;
                drive_item.Tag = drive;

                DirectoryTreeView.Items.Add(drive_item);

                drive_item.Items.Add("...");
            }
        }

        //==========================================================================
        private string GetFullPath(TreeViewItem directoryItem)
        {
            if (directoryItem.Parent is TreeViewItem)
                return System.IO.Path.Combine(GetFullPath(directoryItem.Parent as TreeViewItem),
                                              directoryItem.Header as string);
            else
                return directoryItem.Header as string;
        }

        //==========================================================================
        private void UpdateDirectoryTreeViewItem(TreeViewItem directoryItem)
        {
            directoryItem.Items.Clear();

            foreach (string directory in Directory.GetDirectories(GetFullPath(directoryItem)))
            {
                TreeViewItem directory_item = new TreeViewItem();
                directory_item.Header = System.IO.Path.GetFileName(directory);
                directory_item.Tag = System.IO.Path.GetFileName(directory);
                directory_item.Expanded += DirectoryTreeViewItemExpanded;
                directoryItem.Items.Add(directory_item);

                directory_item.Items.Add("...");
            }
        }

        //==========================================================================
        private void DirectoryTreeViewItemExpanded(object sender, RoutedEventArgs e)
        {
            TreeViewItem directory_item = sender as TreeViewItem;
            if (directory_item.Items.Count == 1)
                if (directory_item.Items[0] is string)
                    UpdateDirectoryTreeViewItem(directory_item);
        }

        //==========================================================================
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            UpdateDirectoryTreeView();
        }

        //==========================================================================
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // Ensure load thread has been aborted...
            lock (m_LoadFiles)
                m_LoadFiles.Clear();
            while (m_LoadThread != null)
                DispatchOperations();
        }

        //==========================================================================
        private void DispatchOperations()
        {
            DispatcherFrame frame = new DispatcherFrame();
            Dispatcher.BeginInvoke(DispatcherPriority.Background,
                (SendOrPostCallback)delegate (object arg) {
                    (arg as DispatcherFrame).Continue = false;
                }, frame);
            Dispatcher.PushFrame(frame);
        }

        //==========================================================================
        private void UpdateDirectoryEntriesListBox()
        {
            if (DirectoryTreeView.SelectedItem == null)
            {
                DirectoryEntriesListBox.Items.Clear();
                DirectoryTextBox.Text = null;
            }
            else
            {
                // Ensure load thread has been aborted...
                lock (m_LoadFiles)
                    m_LoadFiles.Clear();
                while (m_LoadThread != null)
                    DispatchOperations();

                string path = GetFullPath(DirectoryTreeView.SelectedItem as TreeViewItem);

                DirectoryTextBox.Text = path;
                StatusTextBlock.Text = String.Format("Scanning directory {0}...", path);
                StatusProgressBar.Value = 0;
                StatusProgressBar.Visibility = Visibility.Visible;
                JournalListBox.Items.Clear();
                JournalListBox.Items.Add(String.Format("Scanning directory {0}...", path));
                DirectoryEntriesListBox.Items.Clear();

                // Fill queue with the files to load...
                foreach (string directory_entry in Directory.GetFiles(path, "*.svg*"))
                    m_LoadFiles.Enqueue(directory_entry);

                // Start load thread and wait until it has been started...
                new Thread(LoadFiles)
                {
                    IsBackground = true,
                    Priority = ThreadPriority.Lowest,
                    ApartmentState = ApartmentState.STA
                }.Start(path);
                while (m_LoadThread == null)
                    DispatchOperations();

            }
        }

        //==========================================================================
        private void DirectoryTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            UpdateDirectoryEntriesListBox();
        }

        //==========================================================================
        private void DirectoryTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
                DirectoryButton_Click(null, null);
        }

        //==========================================================================
        private TreeViewItem ApplyDirectoryInfo(TreeViewItem parent_item, DirectoryInfo directoryInfo)
        {
            if (directoryInfo.Parent == null)
            {
                foreach (TreeViewItem root_item in DirectoryTreeView.Items)
                    if (String.Compare((string)root_item.Tag, directoryInfo.Name, true) == 0)
                        return root_item;
            }
            else
            {
                TreeViewItem root_item = ApplyDirectoryInfo(null, directoryInfo.Parent);
                if (root_item != null)
                {
                    root_item.IsExpanded = true;

                    foreach (TreeViewItem child_item in root_item.Items)
                        if (String.Compare((string)child_item.Tag, directoryInfo.Name, true) == 0)
                            return child_item;
                }
            }

            return null;
        }

        //==========================================================================
        private void DirectoryButton_Click(object sender, RoutedEventArgs e)
        {
            string path = DirectoryTextBox.Text;
            if (path != null)
                try
                {
                    TreeViewItem item = ApplyDirectoryInfo(null, new DirectoryInfo(path));
                    if (item != null)
                        item.IsSelected = true;
                }
                catch (Exception exception)
                {
                    MessageBox.Show(exception.Message);
                }
        }

        //==========================================================================                
        /// <summary>
        ///   Gets or sets the value of ThumbnailSize of the MainWindow.
        /// </summary>
        public int ThumbnailSize
        {
            get
            {
                return (int)GetValue(ThumbnailSizeProperty);
            }
            set
            {
                SetValue(ThumbnailSizeProperty, value);
            }
        }

        //==========================================================================
        private static void OnThumbnailSizeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            (sender as MainWindow).UpdateDirectoryEntriesListBox();
        }

        //==========================================================================
        private static object CoerceThumbnailSize(DependencyObject sender, object value)
        {
            int thumbnail_size = (int)value;
            if (thumbnail_size <= 0)
                return DependencyProperty.UnsetValue;
            return value;
        }

        //==========================================================================
        /// <summary>
        ///   Identifies the <see cref="ThumbnailSize"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ThumbnailSizeProperty =
            DependencyProperty.Register("ThumbnailSize", typeof(int), typeof(MainWindow), new FrameworkPropertyMetadata(64, OnThumbnailSizeChanged, CoerceThumbnailSize));

        //==========================================================================
        private void ThumbnailSizeTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
                ThumbnailSizeTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        }

    } // class MainWindow 
}
